import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.googlecode.dex2jar.ClassVisitorFactory;
import com.googlecode.dex2jar.DexException;
import com.googlecode.dex2jar.Version;
import com.googlecode.dex2jar.reader.DexFileReader;
import com.googlecode.dex2jar.v3.Main;
import com.googlecode.dex2jar.v3.V3;
import com.googlecode.dex2jar.v3.V3AccessFlagsAdapter;

public class DexFileConvertor {
	private static final Logger log = LoggerFactory.getLogger(Main.class);

	public static void niceExceptionMessage(Logger log, Throwable t, int deep) {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < deep + 1; i++) {
			sb.append(".");
		}
		sb.append(' ');
		if ((t instanceof DexException)) {
			sb.append(t.getMessage());
			log.error(sb.toString());
			if (t.getCause() != null) {
				niceExceptionMessage(log, t.getCause(), deep + 1);
			}
		} else if (t != null) {
			log.error(sb.append("ROOT cause:").toString(), t);
		}
	}

	public static void main(String[] args) {
		log.info(new StringBuilder().append("version:")
				.append(Version.getVersionString()).toString());
		if (args.length == 0) {
			System.err.println("dex2jar file1.dexORapk file2.dexORapk ...");
			return;
		}
		String jreVersion = System.getProperty("java.specification.version");
		if (jreVersion.compareTo("1.6") < 0) {
			System.err.println("A JRE version >=1.6 is required");
			return;
		}

		log.debug("DexFileReader.ContinueOnException = true;");
		DexFileReader.ContinueOnException = true;

		boolean containsError = false;

		for (String file : args) {
			File dex = new File(file);
			File gen = new File(new StringBuilder().append(file).append(".dex2jar.jar").toString());
			log.info("dex2jar {} -> {}", dex, gen);
			try {
				convertFile(dex, gen);
			} catch (Exception e) {
				containsError = true;
				niceExceptionMessage(log, new DexException(e, "while process file: [%s]", new Object[] { dex }), 0);
			}
		}
		log.info("Done.");
		System.exit(containsError ? -1 : 0);
	}

	public static void doData(byte[] data, File destJar) throws IOException {
		final ZipOutputStream zos = new ZipOutputStream(FileUtils.openOutputStream(destJar));

		DexFileReader reader = new DexFileReader(data);
		V3AccessFlagsAdapter afa = new V3AccessFlagsAdapter();
		reader.accept(afa);
		reader.accept(new V3(afa.getAccessFlagsMap(),
				new ClassVisitorFactory() {
					public ClassVisitor create(final String name) {
						return new ClassWriter(1) {
							public void visitEnd() {
								super.visitEnd();
								try {
									byte[] data = toByteArray();
									ZipEntry entry = new ZipEntry(name + ".class");
									zos.putNextEntry(entry);
									zos.write(data);
									zos.closeEntry();
								} catch (IOException e) {
									e.printStackTrace();
								}
							}
						};
					}
				}));
		zos.finish();
		zos.close();
	}

	public static void convertFile(File srcDex) throws IOException {
		convertFile(srcDex, new File(srcDex.getParentFile(), new StringBuilder().append(srcDex.getName()).append(".dex2jar.jar").toString()));
	}

	public static void convertFile(File srcDex, File destJar) throws IOException {
		byte[] data = FileUtils.readFileToByteArray(srcDex);

		if ("dex".equals(new String(data, 0, 3))) {
			doData(data, destJar);
		} else if ("PK".equals(new String(data, 0, 2))) {
			ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(data));
			for (ZipEntry entry = zis.getNextEntry(); entry != null; entry = zis.getNextEntry()){
				if (entry.getName().equals("classes.dex")) {
					data = IOUtils.toByteArray(zis);
					doData(data, destJar);
				}
			}
		} else {
			throw new RuntimeException("the src file not a .dex file or a zip file");
		}
	}
}
